﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection.Emit;
using System.Linq.Expressions;
using System.Reflection;
using PServiceBus.WebSocket.RPC.DTO;
using PServiceBus.Services.Web.DTO;

namespace PServiceBus.WebSocket.RPC.Runtime {
    public partial class WebSocketMessageBus {

        private static Dictionary<string, Func<object, object, object>> _funcs =
            new Dictionary<string,Func<object,object,object>>();

        private static Dictionary<string, Func<object, object>> _funcNps =
            new Dictionary<string, Func<object, object>>();

        private static Dictionary<string, Action<object, object>> _actions = 
            new Dictionary<string,Action<object,object>>();

        private static Dictionary<string, Type> _returnTypes =
            new Dictionary<string, Type>();
        
        private readonly static Type _objectType = typeof(object);
        private readonly static Type[] _objParams = new[] { _objectType, _objectType };

        private static Func<object, object, object> GetFunc<TRequest, TResponse>(MethodInfo meth) {
            var responseType = typeof(TResponse);
            var method = new DynamicMethod(meth.Name + "func", _objectType, _objParams, true);
            var il = method.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Isinst, typeof(WebSocketMessageBus));
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Isinst, typeof(TRequest));
            il.Emit(OpCodes.Callvirt, meth);
            if (responseType.IsValueType)
                il.Emit(OpCodes.Box, responseType);
            il.Emit(OpCodes.Ret);
            return method.CreateDelegate(typeof(Func<object, object, object>)) as Func<object, object, object>;
        }

        private static Func<object, object> GetFunc<TResponse>(MethodInfo meth) {
            var responseType = typeof(TResponse);
            var method = new DynamicMethod(meth.Name + "funcNp", _objectType, new[] { _objectType }, true);
            var il = method.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Isinst, typeof(WebSocketMessageBus));
            il.Emit(OpCodes.Callvirt, meth);
            if (responseType.IsValueType)
                il.Emit(OpCodes.Box, responseType);
            il.Emit(OpCodes.Ret);
            return method.CreateDelegate(typeof(Func<object, object>)) as Func<object, object>;
        }

        private static Action<object, object> GetAction<TCommand>(MethodInfo meth) {
            var method = new DynamicMethod(meth.Name + "action", typeof(void), _objParams, true);
            var il = method.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Isinst, typeof(WebSocketMessageBus));
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Isinst, typeof(TCommand));
            il.Emit(OpCodes.Callvirt, meth);
            il.Emit(OpCodes.Ret);
            return method.CreateDelegate(typeof(Action<object, object>)) as Action<object, object>;
        }

        private static Tuple<MethodInfo, string> GetMethodInfo(LambdaExpression exp) {
            var methodCall =
                (exp.Body is UnaryExpression ? (exp.Body as UnaryExpression).Operand : exp.Body)
                as MethodCallExpression;
            var method = (methodCall.Arguments.Last() as ConstantExpression).Value as MethodInfo;
            var key = method.Name;
            return new Tuple<MethodInfo, string>(method, key);
        }

        private static void Register<TRequest, TResponse>(Expression<Func<WebSocketMessageBus, Func<TRequest, TResponse>>> exp) {
            var tuple = GetMethodInfo(exp);
            _returnTypes[tuple.Item2] = tuple.Item1.GetParameters().First().ParameterType;
            _funcs[tuple.Item2] = GetFunc<TRequest, TResponse>(tuple.Item1);
        }

        private static void Register<TResponse>(Expression<Func<WebSocketMessageBus, Func<TResponse>>> exp) {
            var tuple = GetMethodInfo(exp);
            _funcNps[tuple.Item2] = GetFunc<TResponse>(tuple.Item1);
        }

        private static void Register<TCommand>(Expression<Func<WebSocketMessageBus, Action<TCommand>>> exp) {
            var tuple = GetMethodInfo(exp);
            _returnTypes[tuple.Item2] = tuple.Item1.GetParameters().First().ParameterType;
            _actions[tuple.Item2] = GetAction<TCommand>(tuple.Item1);
        }

        private static void Register(Expression<Func<WebSocketMessageBus, Action<NameObject>>> exp) {
            Register<NameObject>(exp);
        }

        private static void Register(Expression<Func<WebSocketMessageBus, Action<SubscriptionInfo>>> exp) {
            Register<SubscriptionInfo>(exp);
        }

        private static void Register(Expression<Func<WebSocketMessageBus, Action<TopicInfo>>> exp) {
            Register<TopicInfo>(exp);
        }

        private static void Register(Expression<Func<WebSocketMessageBus, Action<PublishTopicInfo>>> exp) {
            Register<PublishTopicInfo>(exp);
        }
    }
}
